February 21, 2016

Leaflet and Mapping in R

Josh Pepper, February 21st 2016

Welcome! While we're waiting:

Introduction

Outline

  • Setup
  • Berkeley crime maps
    • GGPLOT2
    • Intro to Leaflet
    • Markers and Popups
    • Color coding
    • Legends
  • Yosemite peaks map
    • Polygons
    • Icons
    • Background

(hint: press w to view slideshow in wide mode)

Setup

This workshop/tutorial will walk you through the basics of using the Leaflet mapping package in R. You can follow along in any of the three formats:

  • Tutorial Page (mapping-in-r.html)
  • Raw code (mapping-in-R-RAW.html or mapping-in-R-RAW.Rmd)
  • Slides (mapping-in-r-slides.html)

To begin, lets set up our packages and environment.

Installing packages

To install Leaflet, use the following command install.packages("leaflet"). We will be using other packages in this tutorial. If you don't already have any of the following packages installed, you can use this code to do so:

install.packages("leaflet")
install.packages("RColorBrewer")
install.packages("rgdal")
install.packages("raster")
install.packages("ggplot2")
install.packages("ggmap")
install.packages("magrittr")

Loading packages

You only instal a package once, but each time you want to use it, you have to load it with library()

library(leaflet)
library(RColorBrewer)
library(rgdal)
library(raster)
library(ggplot2)
library(ggmap)
library(magrittr)

Set working directory

Loading crime data

Now that we've loaded the crime data, lets take a look at the first few lines of our new variable titled berkeleyCrime:

berkeleyCrime[1:5,c("OFFENSE","EVENTDT","EVENTTM","lat","long")]
##                    OFFENSE    EVENTDT EVENTTM      lat      long
## 1 THEFT MISD. (UNDER $950) 01/30/2016   16:30 37.87122 -122.2682
## 2 THEFT MISD. (UNDER $950) 01/11/2016   10:57 37.86866 -122.2592
## 3                VANDALISM 09/05/2015   13:55 37.85034 -122.2728
## 4    ASSAULT/BATTERY MISD. 12/05/2015   00:00 37.86780 -122.2508
## 5        DOMESTIC VIOLENCE 11/12/2015   12:20 37.86687 -122.2951

Mapping with GGPLOT

GGPLOT map

    map <- ggplot() + 
           geom_point(data=berkeleyCrime, aes(x=long, y=lat))
    map

Add a background

We can use the function get_map() to pull a background map from google, OpenStreetMap (OSM) or Stamen.

    background <- get_map(location=c(lon = mean(berkeleyCrime$long),
                                     lat = mean(berkeleyCrime$lat)),
                          zoom=14,
                          maptype = "terrain",
                          source="google",
                          color="bw")
    
    map <- ggmap(background) + coord_equal() + 
      geom_point(data=berkeleyCrime, aes(x=long, y=lat, alpha=0.3, size=7, color=CVLEGEND)) +
      scale_size_continuous(range = c(3), guide=FALSE) +
      scale_alpha_continuous(range = c(.3), guide=FALSE)
    
    map

Add a background

Add a background

Some things you can try to change this map:

  • Change maptype from "terrain" to "satellite"
  • Change color from "bw" to "color".
  • In the console, type ?get_map to view other options to customize your background map

Leaflet Map: Crime Map

Intro to Leaflet

Leaflet is one of the most popular open-source JavaScript libraries for interactive maps. We can easily enable Leaflet and pull up the standard OpenStreetMap (OSM) baselayer using the following command:

leaflet() %>% addTiles() %>% addMarkers(lng = -122.258, lat = 37.870)

Try panning around and zooming in on this map, and notice how more and more data is loaded.

Piping %>%

So what's with the %>% used in the previous example??

Throughout this presentation, I'll use something called a pipe which looks like this: %>% Pipes are a way of passing the result of one function into the next function. For example, the traditional way of pulling up a map with leaflet, adding tiles and then setting the view would be as follows:

addMarkers(addTiles(leaflet()), lng = -122.258, lat = 37.870).

Piping lets us flip the notation inside out, so it reads more naturally:

leaflet() %>% addTiles() %>% addMarkers(lng = -122.258, lat = 37.870).

See how the code in this second example reads like a recipe of what we want to do:

  1. enable leaflet: leaflet() %>%
  2. add background tiles: addTiles() %>%
  3. set the view of the map: addMarkers(lng = -122.258, lat = 37.870)

You can read more about using pipes here and here.

Crime Map

Lets use out new knowledge of Leaflet to map the first 100 crimes in our berkeleyCrime dataset:

leaflet() %>% addTiles() %>% addMarkers(data=berkeleyCrime[1:100,])

Adding other layers

For more information, pull up the help on any of the add functions in leaflet (e.g. ?addMarkers):

addMarkers(map, lng = NULL, lat = NULL, layerId = NULL, group = NULL, icon = NULL,
           popup = NULL, options = markerOptions(), clusterOptions = NULL,
           clusterId = NULL, data = getMapData(map))

addCircleMarkers(map, lng = NULL, lat = NULL, radius = 10, layerId = NULL, 
    group = NULL, stroke = TRUE, color = "#03F", weight = 5, opacity = 0.5, 
    fill = TRUE, fillColor = color, ....)

addTiles(map, urlTemplate = "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
         attribution = NULL, layerId = NULL, group = NULL, options = tileOptions())

addCircles(...)
addPolylines(...)
addRectangles(...)
addPolygons(...)
addPopups()
...

Popups

In this example, we change our markers to circleMarkers so we can adjust the color (and radius if we wish) and also add a popup that displays information when we click on the circle:

leaflet() %>% addTiles() %>%
      addCircleMarkers(data=berkeleyCrime[1:100,],
                       stroke = FALSE, fillOpacity = 0.5, radius=8,
                       popup=~paste("<strong>Offense:</strong>",CVLEGEND,
                       "<br>",
                       "<strong>Date:</strong>",EVENTDT,
                       "<br>",
                       "<strong>Time:</strong>",EVENTTM)
                       )

Popups

  • Changing markers to circleMarkers
  • Adding popup to circleMarkers

Color Coding by Crime

    #Create color palette
    offenseColor <- colorFactor(rainbow(25), berkeleyCrime$CVLEGEND)
    
    leaflet(berkeleyCrime[1:100,]) %>%
      addProviderTiles("CartoDB.Positron") %>%
      addCircleMarkers(
        stroke = FALSE, fillOpacity = 0.5, radius=6,
        color = ~offenseColor(CVLEGEND),
        popup = ~paste("<strong>Offense:</strong>",CVLEGEND,
                       "<br>",
                       "<strong>Date:</strong>",EVENTDT,
                       "<br>",
                       "<strong>Time:</strong>",EVENTTM)
      ) %>%
      addLegend(title = "Type of Offense", pal = offenseColor,
                values = ~CVLEGEND, opacity = 1, position="bottomleft")

Color Coding by Crime

  • Adding color coding by crime type
  • Adding legend

Clustering

If we want to view many crimes, say all 4,598 crimes in this dataset, we may want to cluster our results. Displaying that many points at once will slow down our computer, and it just hard to visually process. Do cluster results in leaflet, we use the clusterOptions variable with markers or circleMarkers.

leaflet(berkeleyCrime) %>%
      addProviderTiles("CartoDB.Positron") %>%
      addCircleMarkers(
        stroke = FALSE, fillOpacity = 0.5, radius=6, color = ~offenseColor(CVLEGEND),
        clusterOptions = markerClusterOptions(spiderfyDistanceMultiplier=2, maxClusterRadius=60),
        popup = ~paste("<strong>Offense:</strong>",CVLEGEND,
                       "<br>",
                       "<strong>Date:</strong>",EVENTDT,
                       "<br>",
                       "<strong>Time:</strong>",EVENTTM)
      ) %>%
      addLegend(title = "Type of Offense", pal = offenseColor,
                values = ~CVLEGEND, opacity = 1, position="bottomleft")

Clustering Map

Map #2: Yosemite Peaks

Data extraction from OSM

For this example, we'll use OpenStreetMap (OSM) data on the peaks in Yosemite National Park. This OSM data is extracted from TurboOverpass. Come to a later workshop to learn how to do this!

Adding Polygons

Loading a shapefile of Yosemite National Park and a section of the PCT.

#load data
yosemite <- readOGR("./data/yosemite", "yosemite", verbose = FALSE) #yosemite boundary shapefile
PCT <- readOGR("./data/PCT.geojson", "OGRGeoJSON", verbose=FALSE)

#make map
leaflet() %>% addTiles() %>% addPolylines(data = yosemite) %>% addPolylines(data = PCT, color="red")

Map of peaks

#load peaks points
peaks <- readOGR("./data/peaks.geojson", "OGRGeoJSON", verbose=FALSE)

#convert peak values to numeric
peaks <- subset(peaks, peaks@data$ele!="0")
peaks@data$ele <- round(as.numeric(levels(peaks@data$ele))[peaks@data$ele] * 3.28084)

#make map of peaks
leaflet() %>%
  addTiles() %>%
  addCircleMarkers(data=peaks, weight=0, radius=8, fillOpacity=0.6,
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele,"feet")) %>%
  addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10), fill=FALSE, color="red")

Map of peaks

Add sizing and mountain icons

Similarly to how we colored crimes by type of crime, we can size these circles based on their elevation using the code: radius=~ele/1000.

I've also added a marker using an icon to the subset of all peaks that are over 10,000 feet. To do this, we first set the icon setting using the function icons() and then usingaddMarkers with the subset of peaks data=subset(peaks, peaks@data$ele>10000)

#mountain icon settings
peakIcons <- icons(
  iconUrl = "./data/Mountain-512.png",
  iconWidth = 15, iconHeight = 15,
  iconAnchorX = 7.5, iconAnchorY = 8.5
  )

leaflet() %>%
  addTiles() %>%
  addCircleMarkers(data=peaks, weight=0, radius=~ele/1000, fillOpacity=0.7, 
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele)) %>%
  addMarkers(data=subset(peaks, peaks@data$ele>10000),
             icon = peakIcons,
             popup=~paste("Name: ",name,"<br>Elevation: ",ele)) %>%
  addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10), fill=FALSE, color="red")

Add sizing and mountain icons

  • Sizing based on elevation
  • Mountain icons for peaks >10,000 ft.

Legends

Lets add some color to our circles. Do do this, we create a palette, just like we did for Berkeley crimes. In this example, there is an extra line where I first define the palette spectral so I can flip the color scheme. Then we map elevations to the palette in the line that begins eleColor.

To add a legend to the map, we use the function addLegend() and assign the pal to the mapped palette eleColor.

#create color scale from elevation
spectral <- brewer.pal(11, "Spectral")  %>% rev()
eleColor <- colorBin(spectral, peaks@data$ele)

leaflet(peaks) %>%
  addTiles() %>%
  addCircleMarkers(weight=0, radius=~ele/1000, fillOpacity=0.7, color = ~eleColor(ele),
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele)) %>%
  addMarkers(data=subset(peaks,peaks@data$ele>10000),
             icon = peakIcons,
             popup=~paste("Name: ",name,"<br>Elevation: ", ele)) %>%
    addLegend(title = "Peak Elevation", pal = eleColor,
            values = ~ele, opacity = 1, position="bottomleft") %>%
    addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10), fill=FALSE, color="blue")

Legends

  • Color coding using colorBin()

Legends

  • Color coding using eleColor <- colorBin(spectral, peaks@data$ele)

Basemaps

You can use the function addProviderTiles() to change the background map, sometimes called the 'basemap'. To view the options for background maps, visit http://leaflet-extras.github.io/leaflet-providers/preview/.

So we don't have to retype our code every time, lets save our map (without a basemap) as a variable:

peakMap <- leaflet() %>%
  addCircleMarkers(data=peaks, weight=0, radius=~ele/1000, fillOpacity=0.7, color = ~eleColor(ele),
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele)) %>%
  addLegend(title = "Peak Elevation", pal = eleColor,
            values = peaks@data$ele, opacity = 1, position="bottomleft") %>%
  addMarkers(data=subset(peaks,peaks@data$ele>10000),
             icon = peakIcons,
             popup=~paste("Name: ",name,"<br>Elevation: ",ele))

Great, now we can call peakMap and just add in different types of basemaps using addProviderTiles

Satellite Imagery

peakMap %>% addProviderTiles("Esri.WorldImagery")

Positron

peakMap %>% addProviderTiles("CartoDB.Positron")

Mapbox

mapbox <- "http://api.tiles.mapbox.com/v4/mapbox.outdoors/{z}/{x}/{y}.png?access_token=pk.eyJ1Ijoiam9zaHBlcHBlciIsImEiOiJuTWdrY2k4In0.HCCXtgU04scrTB_-ON4kjA"
peakMap %>% addTiles(urlTemplate = mapbox)

Weather Maps

You can even pull in weather data. In this exmaple, I've overlaid weather data on top of the CartoDB.Positron basemap.

leaflet() %>% addProviderTiles("CartoDB.Positron") %>%
  addProviderTiles("OpenWeatherMap.Temperature", options=providerTileOptions(opacity=0.2)) %>%
  setView(lng = -122.2579335, lat = 37.8700441, zoom=4)

Putting it all together: Layer Switcher

To add 'controls' to our map, we use the function addLayersControl(). We assign a group to each basemap and layer (polygon, marker, line, etc). Then layer switcher allows us to toggle between different basemaps and turn on/off each of the layers.

leaflet() %>%
  addCircleMarkers(data=peaks, weight=0, radius=~ele/1000,
                   fillOpacity=0.7, color = ~eleColor(ele),
                   popup=~paste("Name: ",name,"<br>Elevation: ",ele),
                   group="Peaks") %>%
  addLegend(title = "Peak Elevation", pal = eleColor,
            values = peaks@data$ele, opacity = 1, position="bottomleft") %>%
  addMarkers(data=subset(peaks,peaks@data$ele>10000),
             icon = peakIcons, popup=~paste("Name: ",name,"<br>Elevation: ",ele),
             group="Mountain Icons") %>%
  addPolylines(data=yosemite, fillOpacity = 0, dashArray=c(10,10),
               fill=FALSE, color="#3288BD",
               group="Yosemite") %>%
  addPolylines(data=PCT, dashArray=c(10,10),
               fill=FALSE, color="#9E0142",
               group="PCT") %>%
  #continued on next slide...

Putting it all together: Layer Switcher (continued)

#add basemaps
  addProviderTiles("CartoDB.Positron", group="Simple") %>%
  addProviderTiles("Esri.WorldImagery", group="Satellite") %>%
  addTiles(urlTemplate = mapbox, group="Outdoors") %>%
  addLayersControl(
    baseGroups = c("Simple", "Satellite", "Outdoors"),
    overlayGroups = c("Peaks","Mountain Icons","Yosemite","PCT"),
    options = layersControlOptions(collapsed = FALSE)
  )

Putting it all together: Layer Switcher